#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <syslog.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <pthread.h>
#include "server.h"
#include "../clsv/clsv.h"
#include "../clsv/ipc/sig.h"
#include "../clsv/log.h"
#include "listADT.h"

#define	HOST		"localhost"
#define	PORT		"4500"

#define TIMEOUT   4

#define	FILE_BUFFER	MAX_DATA

extern counter;
extern int errno;
extern int useLog;
TsrvData srvData;

static pid_t servpid;
extern int errno;

typedef struct sessionInfo {
	int session;
	char nick[MAX_NICK+1];
	unsigned long threadid;
} sessionInfoT;

typedef struct cliInfo {
	
	int session;
	int time;
} cliInfoT;

typedef struct Tmsg
{
	long mtype; 			/* Message type */
	char mtext[MSG_MAX]; 	/* Message text */
} Tmsg;

struct server_info {
	int mqid;
	listADT list;
};

static void (*proc[])(CLSV_BUFFER * p, int * session, threadInfoT * tinfo) =
{
	process_conn,
	process_conn,
	process_lists,
	process_listu,
	process_disc,
	process_MessR,
};

struct server_info _info;

pthread_mutex_t mutexlist;

int myMsgsnd(Tmsg * msg, int time, int mqid);

int myMsgrcv(Tmsg * msg, int time, int pid, int mqid);

extern int alrm;

static void * processCli(void * sess);

int
compare( listElementT elem1, listElementT elem2)
{
	if(((sessionInfoT *)elem1)->session > ((sessionInfoT *)elem2)->session)
		return 1;
	else 
	{
		if(((sessionInfoT *)elem1)->session == ((sessionInfoT *)elem2)->session)
			return strcmp(((sessionInfoT *)elem1)->nick, ((sessionInfoT *)elem2)->nick);
		else
			return -1;
	}
}

listElementT
copy(listElementT  elem)
{
	sessionInfoT * ret, * aux;

	if((ret = malloc(sizeof(sessionInfoT))) == NULL)
	{
		myError(ERROR_MALLOC);
		return NULL;
	}
	aux = (sessionInfoT *) elem;
	ret->session = aux->session;
	ret->threadid = aux->threadid;
	strcpy(ret->nick, aux->nick);

	return ret;
}

void
freeNode(listElementT  elem)
{
	free((sessionInfoT *)elem);
}


int
main(int argc, char *argv[])
{
	int mqid, pid, i=1;
	int error, numthread = 0;
	pthread_t threads[MAX_THREADS];

	mqid = msgget(MSG_KEY, 0);
	msgctl(mqid, IPC_RMID, NULL);

	mqid = msgget(KEY_MSG, 0);
	msgctl(mqid, IPC_RMID, NULL);
	
	do
	{
		if( (mqid = msgget(KEY_MSG+i+1, 0)) != -1 );
			msgctl(mqid, IPC_RMID, NULL);
		i++;	
	} while( mqid != -1 );

	/* Server data init */
	srvData.cliSess = NULL;
	srvData.srvSess = NULL;

	/* Log initialize */
	useLog = LOG_YES;
	logOpen("Chat Server.");

	/* Get the configuration parameters */
	if( getConf(argc, argv, &srvData) )
	{
		logEvent("Invalid parameters.");
		return ERROR_PARAMS;
	}
	
	/* Check for other instances */
	error = checkInstance(&srvData);
	switch( error )
	{
		case ERROR_OK:
			logEvent("Instance OK.");
			break;
		case ERROR_INST:
			logEvent("Another instance is running.");
			exit(1);
			break;
		default:
			logEvent("Instance initialization failure.");
			exit(1);
			break;
	}

	/* Server initialization */
	if (initSessServer(srvData.ipc, srvData.port, &(srvData.srvSess))
								!= ERROR_OK )
	{
		logEvent("Session initialization failed.");
		return 1;
	}

	/* Close server on SIGHUP, SIGTERM, etc */
	intrHandler();
	
	/*creating internal server queue for messages*/
	if((_info.mqid = msgget(MSG_KEY, QPERM | IPC_CREAT | IPC_EXCL)) == -1)
	{
		if( errno == EEXIST )
			fatal("Error: Message queue already exists");
		else
			fatal("Error: Can't create queue, unexpected error");
	}

	/*creating session servers list*/
	if((_info.list = listNew(compare, copy, freeNode)) == NULL)
	{
		fprintf(stderr, "Can't create new list for server information.\n");
		exit(EXIT_FAILURE);
	}
	/*if((_info.tlist = listNew(tcompare, tcopy, tfreeNode)) == NULL)
	{
		fprintf(stderr, "Can't create new list for server information.\n");
		exit(EXIT_FAILURE);
	}*/
	pthread_mutex_init(&mutexlist, NULL);
	/* Server */
	forever
	{
		counter++;
		if( listenSess(srvData.srvSess, &(srvData.cliSess)) != ERROR_OK )
		{
			logEvent("Error listening.\n");
			return 1;
		}
		logEvent("\nNew Client!\n");
		pthread_create(&threads[numthread], NULL, processCli, (void *)srvData.cliSess);
		numthread++;
		srvData.cliSess = NULL;	
	}		

	/* Server shutdown */
	pthread_mutex_destroy(&mutexlist);
	exitServer(0);

	return ERROR_OK;
}


/*--------------------------------------------------------------------------*/

int
getConf(int argc, char ** argv, TsrvData *srvData)
{
	int index= 1;
	static Tfifo fifo;
	static Tshm shm;
	static int key;

	/* Usage: server IPC PORT */
	if( argc != 2 )
		return ERROR_DEFAULT;

	if( strcmp(argv[index], USE_FIFO_STR) == 0 )
	{
		system("rm /tmp/chat*");
		srvData->ipc = SESS_FIFO;
		srvData->instance = INSTANCE_KEY_FIFO;
		fifo.write = KEY_FIFO_SND;
		fifo.read = KEY_FIFO_RCV;
		srvData->port = &fifo;
	}
	else if( strcmp(argv[index], USE_TCP_STR) == 0 )
	{
		srvData->ipc = SESS_TCP;
		srvData->instance = INSTANCE_KEY_TCP;
		srvData->port = KEY_TCP;
	}
	else if( strcmp(argv[index], USE_SHM_STR) == 0 )
	{
		srvData->ipc = SESS_SHM;
		srvData->instance = INSTANCE_KEY_SHM;
		shm.keySnd = KEY_SHM_SND;
		shm.keyRcv = KEY_SHM_RCV;
		srvData->port = &shm;
	}
	else if( strcmp(argv[index], USE_MSG_STR) == 0 )
	{
		srvData->ipc = SESS_MSG;
		srvData->instance = INSTANCE_KEY_MSG;
		key = KEY_MSG;
		srvData->port = &key;
	}
	else
		return ERROR_PARAMS;

	return ERROR_OK;
}

int
checkInstance(TsrvData *srvData)
{
	int ans;

	if( (ans= openSem(srvData->instance,1)) == ERROR_EXIST )
		ans= createSem(srvData->instance, 1, INSTANCE_PERMS);

	if( ans >= 0 )
		return pInstanceSem(ans, 0);
	else
		return ans;

}

int
intrHandler(void)
{
	struct sigaction act;

	/* Block other signals */
	if( sigfillset(&(act.sa_mask)) == -1)
		return ERROR_DEFAULT;
	if( sigdelset(&(act.sa_mask),SIGALRM) == -1)
		return ERROR_DEFAULT;

	/* Close server on exit */
	act.sa_handler = exitServer;

	if( sigaction(SIGINT, &act, NULL) == -1)
		return ERROR_DEFAULT;
	if( sigaction(SIGSEGV, &act, NULL) == -1)
		return ERROR_DEFAULT;
	return ERROR_OK;
}

void
exitServer(int s)
{
	if( srvData.cliSess != NULL )
		closeSess(srvData.cliSess);
//	if( srvData.srvSess != NULL )
//		closeSess(srvData.srvSess);

	srvData.cliSess= NULL;
	srvData.srvSess= NULL;
	logClose();
	exit(ERROR_UNEX);
}


void *
doChat(void * threadinfo)
{
	threadInfoT * tinfo;
	Tmsg msg;
	CLSV_BUFFER buffer;
	int session = 1;
	printf("doChat:entro\n");
	tinfo = threadinfo;
	
	forever
	{
		errno = 0;
		memset(&msg, 0, sizeof(Tmsg));
		msgrcv(_info.mqid, &msg, sizeof(Tmsg), labs((long)tinfo->parentid), 0);
		printf("doChat: despues de msgrcv mtype: %d\n", msg.mtype);
		printf("doChat: tinfo->parentid: %d \n", tinfo->parentid);
		//if(msg.mtype == tinfo->parentid)
		//{
			memcpy(&buffer, msg.mtext, sizeof(CLSV_BUFFER));
			printf("doChat: opcode:%d\n",buffer.opcode);
			if(buffer.opcode == CHATS)
				process_MessS(&buffer, &session, tinfo);
		//}
	}
}

static void *
processCli(void * sessionadt)
{
	Tmsg msg;
	CLSV_BUFFER buffer;
	sessADT sess;
	long pid;
	int session = 0, chatthread = 0;
	pthread_t thread2;
	threadInfoT tinfo;
	
	pthread_mutex_init(&(tinfo.mutexsnd), NULL);
	sess = sessionadt;
	pid = (long) pthread_self();
	
	do{
		printf("processCli: loop\n");
		errno = 0;
		memset(&buffer, 0, sizeof(CLSV_BUFFER));
		//pthread_mutex_lock(&(tinfo.mutexsnd));
		getMsg(sess, &buffer, sizeof(CLSV_BUFFER), 0);
		//pthread_mutex_unlock(&(tinfo.mutexsnd));
		if(errno != EINTR)
		{
			printf("processCli1: buffer.opcode: %d\n", buffer.opcode);
			if(chatthread && buffer.opcode != CHATR)
				pthread_mutex_lock(&(tinfo.mutexsnd));
			if(buffer.opcode > 0 && buffer.opcode < MAX_OPCODE)
				proc[buffer.opcode](&buffer, &session, &tinfo);
			if(chatthread && buffer.opcode != CHATR)
				pthread_mutex_unlock(&(tinfo.mutexsnd));
		}
		printf("processCli: antes msgrcv mqid2\n");
		if(session > 0 && !chatthread)
		{
			tinfo.parentid = pid;
			tinfo.session = sess;
			pthread_create(&thread2, NULL, doChat, (void *)&tinfo);
			chatthread = 1;
		}
		

	} while(session > 0);

	if( closeSess(srvData.cliSess) != ERROR_OK )
		fprintf(stderr, "Error closing session.\n");
	srvData.cliSess= NULL;
	pthread_mutex_destroy(&(tinfo.mutexsnd));
	pthread_exit(NULL);
	return;
}


/*----------------------------------------------------------------------------*/


void
process_conn(CLSV_BUFFER * data, int * session, threadInfoT * tinfo)
{
	Tmsg msg;
	CLSV_BUFFER buffer;
	sessionInfoT sess, *answer;
	int ans;
	
	sess.session = data->data.clsv_conn.session;
	strcpy(sess.nick, data->data.clsv_conn.nick);
	
	pthread_mutex_lock(&mutexlist);
	answer = GetDatoComp(_info.list, &session);
	pthread_mutex_unlock(&mutexlist);
	
	buffer.opcode = CONN;
	if(answer != NULL)
	{
		buffer.data.svcl_conn.status = ERROR_NICK_EXISTS;
	}
	else
	{
		sess.threadid = pthread_self();
		pthread_mutex_lock(&mutexlist);
		ans = listInsert(_info.list, &sess);
		pthread_mutex_unlock(&mutexlist);
		
		if(!ans)
			buffer.data.svcl_conn.status = ERROR_NICK_EXISTS;
		else
		{
			buffer.data.svcl_conn.status = OK_;
			*session = data->data.clsv_conn.session;
		}
	}
	//pthread_mutex_lock(&(tinfo->mutexsnd));
	sendMsg( srvData.cliSess, &buffer, sizeof(CLSV_BUFFER));	
	//pthread_mutex_unlock(&(tinfo->mutexsnd));
	return;
}

void
process_lists(CLSV_BUFFER * data, int * session, threadInfoT * tinfo)
{
	Tmsg msg;
	CLSV_BUFFER buffer;
	sessionInfoT * sess;
	int i=0, num = 0;
	
	printf("process_lists: _info.list \n");
	
	buffer.opcode = LISTS;

	pthread_mutex_lock(&mutexlist);
	
	ToBegin(_info.list);
	while((sess = GetDato(_info.list)) != NULL)
	{
		if(num != sess->session)
		{
			i += sprintf((buffer.data.svcl_lists.list)+i, "%d\n", sess->session);
			num = sess->session;
		}
	}
	pthread_mutex_unlock(&mutexlist);
	(buffer.data.svcl_lists.list)[i] = 0;
	if(i == 0)
		buffer.data.svcl_lists.status = ERROR_NO_SESSION;
	else
		buffer.data.svcl_lists.status = OK_;
	
	//pthread_mutex_lock(&(tinfo->mutexsnd));
	printf("process_lists: status: %d\n", buffer.data.svcl_lists.status);
	sendMsg( srvData.cliSess, &buffer, sizeof(CLSV_BUFFER));
	//pthread_mutex_unlock(&(tinfo->mutexsnd));	
	return;
}

void
process_listu(CLSV_BUFFER * data, int * session, threadInfoT * tinfo)
{
	CLSV_BUFFER buffer;
	Tmsg msg;
	sessionInfoT * sess;
	int end = 0, i = 0;

	pthread_mutex_lock(&mutexlist);

	ToBegin(_info.list);
	
	while(!end && (sess = GetDato(_info.list)) != NULL)  
	{
		if(sess->session == data->data.clsv_listu.session)
			i += sprintf((buffer.data.svcl_listu.list)+i, "%s\n", sess->nick);
		
		if(sess->session > data->data.clsv_listu.session)
			end = 1;

	}
	pthread_mutex_unlock(&mutexlist);
	if(i == 0)
		buffer.data.svcl_listu.status = ERROR_NO_USERS;
	else
		buffer.data.svcl_listu.status = OK_;

	buffer.opcode = LISTU;
	//pthread_mutex_lock(&(tinfo->mutexsnd));
	printf("process_listu: status: %d\n", buffer.data.svcl_listu.status);
	//sleep(1);
	sendMsg( srvData.cliSess, &buffer, sizeof(CLSV_BUFFER));	
	//pthread_mutex_unlock(&(tinfo->mutexsnd));
	return;
}

void
process_MessR(CLSV_BUFFER * data, int * session, threadInfoT * tinfo)
{
	Tmsg msg;
	sessionInfoT * sess;
	CLSV_BUFFER buffer;
	int error = 0, end = 0;

	printf("process_chMessR: msg: %s nick:%s  session:%d\n", data->data.clsv_chat.msg, data->data.clsv_chat.nick, * session);
	buffer.opcode = CHATS;
	buffer.data.svcl_chat.status = OK_;
	strcpy(buffer.data.svcl_chat.msg, data->data.clsv_chat.msg);
	strcpy(buffer.data.svcl_chat.nick, data->data.clsv_chat.nick);

	pthread_mutex_lock(&mutexlist);
	printf("process_chMessR: mutextlist\n");
	ToBegin(_info.list);
	while(!error && !end && (sess = GetDato(_info.list)) != NULL)
	{
			/* send the message to all clients in the session */
			printf("process_chMessR: entro while\n");
			if(sess->session == *session)
			{
				printf("process_chMessR: mande msg a %ld\n", sess->threadid);
				msg.mtype = labs((long)sess->threadid);
				memcpy(&(msg.mtext), &buffer, sizeof(CLSV_BUFFER));

				if(msgsnd(_info.mqid, &msg, sizeof(CLSV_BUFFER), 
															IPC_NOWAIT) == -1)
				{
					perror("process_chMessR: error en el loop\n");
					error = 1;
				}
				if(sess->session > *session)
					end = 1;
			}
	}
	pthread_mutex_unlock(&mutexlist);

}

void
process_MessS(CLSV_BUFFER * data, int * session, threadInfoT * tinfo)
{
	printf("process_chMessS: pid: %d\t msg: %s nick:%s \n", pthread_self(), data->data.svcl_chat.msg, data->data.svcl_chat.nick );
	//pthread_mutex_lock(&(tinfo->mutexsnd));
	sendAns( tinfo->session, data, sizeof(CLSV_BUFFER));
	//pthread_mutex_unlock(&(tinfo->mutexsnd));
}

void
process_disc(CLSV_BUFFER * data, int * session, threadInfoT * tinfo)
{
	sessionInfoT sess;
	CLSV_BUFFER buffer;

	printf("process_disc: session: %d nick:%s\n", data->data.clsv_disc.session,  data->data.clsv_disc.nick);
	sess.session = data->data.clsv_disc.session;
	strcpy(sess.nick, data->data.clsv_disc.nick);
	printf("process_disc: session: %d nick:%s\n", sess.session, sess.nick);
	pthread_mutex_lock(&mutexlist);
	listElemDelete(_info.list, &sess);
	pthread_mutex_unlock(&mutexlist);
	
	*session = -1;	
	return;
}
















